package cn.com.sgcc.dlt698;

import cn.com.sgcc.dlt698.data.*;
import cn.com.sgcc.dlt698.util.NumberConvert;
import cn.com.sgcc.dlt698.util.ProtocolData;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;

import java.util.*;

public class Frame698 {
    private static void checkFrame(String frame698Str) {
        if (StringUtils.isEmpty(frame698Str) || !NumberConvert.isHexStr(frame698Str))
            throw new RuntimeException("ERROR_STRING_FORMAT");

        int len = frame698Str.length();

        if (len < 24 || len % 2 != 0)
            throw new RuntimeException("ERROR_STRING_LENGTH");

        if ((!frame698Str.substring(0, 2).equalsIgnoreCase("68") || !frame698Str.substring(len - 2, len)
                .equalsIgnoreCase("16")))
            throw new RuntimeException("ERROR_STRING_FORMAT");

        if (!Objects.requireNonNull(NumberConvert.hexStrReverse(frame698Str.substring(2, 6), 0, 4))
                .equalsIgnoreCase(NumberConvert.toHexStr((len - 4) / 2, 4)))
            throw new RuntimeException("ERROR_STRING_LENGTH");
    }

    private static ImmutablePair<String, String> getlinkData(String frame698Str) {
        int offset = 0;

        String start = frame698Str.substring(offset, offset + 2);
        offset += 2;

        int len = Integer.parseInt(
                Objects.requireNonNull(NumberConvert.hexStrReverse(frame698Str.substring(offset, offset + 4), 0, 4)),
                16);
        offset += 4;

        String C = frame698Str.substring(offset, offset + 2);
        offset += 2;

        String saType = NumberConvert.toBinaryStr(Integer.parseInt(frame698Str.substring(offset, offset + 2), 16), 8);
        offset += 2;
        if (!saType.startsWith("0000"))
            throw new RuntimeException("Unsupported");

        int    serverAddressLength = Integer.parseInt(saType.substring(4), 2);
        String serverAddress       = frame698Str.substring(offset, offset + (serverAddressLength + 1) * 2);
        serverAddress = NumberConvert.hexStrReverse(serverAddress, 0, serverAddress.length());
        offset += (serverAddressLength + 1) * 2;

        //CA
        offset += 2;

        //linkData CRC
        offset += 4;

        int    linkDataLen = frame698Str.length() - 6 - offset;
        String linkData    = frame698Str.substring(offset, offset + linkDataLen);
        offset += linkDataLen;

        return ImmutablePair.of(serverAddress, linkData);
    }

    private static List<CSD> readRCSD(ProtocolData protocolData) {
        List<CSD> header = new ArrayList<>();

        int len = protocolData.readInt(2);
        for (int i = 0; i < len; i++) {
            CSD csd = new CSD();
            csd.setType(protocolData.readInt(2));

            String oi = protocolData.readStr(4);
            protocolData.readStr(4);

            OAD oad;
            if (csd.getType() == 0) {
                oad = new OAD();
                oad.setOi(oi);
            } else if (csd.getType() == 1) {
                int seqLen = protocolData.readInt(2);

                oad = new ROAD();
                oad.setOi(oi);

                List<OAD> oads = new ArrayList<>();
                for (int j = 0; j < seqLen; j++) {
                    OAD oad_ = new OAD();

                    oad_.setOi(protocolData.readStr(4));
                    protocolData.readStr(4);

                    oads.add(oad_);
                }
                ((ROAD) oad).setOads(oads);
            } else {
                throw new RuntimeException("unsupported");
            }

            csd.setOad(oad);

            header.add(csd);
        }

        return header;
    }

    private static List<Data> readData(ProtocolData protocolData) {
        List<Data> datas = new ArrayList<>();

        String type = protocolData.readStr(2);

        switch (type) {
        case "00": {
            datas.add(new Data(type, null));
        }
        break;
        case "01", "02": {
            int len = protocolData.readInt(2);

            for (int i = 0; i < len; i++) {
                var d = readData(protocolData);
                datas.add(d.getFirst());
            }
        }
        break;
        case "05", "06": {
            datas.add(new Data(type, protocolData.readStr(8)));
        }
        break;
        case "10", "12": {
            datas.add(new Data(type, protocolData.readStr(4)));
        }
        break;
        case "55": {
            int octetLen = protocolData.readInt(2);
            int fstByte  = protocolData.readInt(2);
            if (fstByte + 1 != octetLen - 1) {
                throw new RuntimeException("unsupported");
            }
            datas.add(new Data(type, protocolData.readStr((fstByte + 1) * 2)));
        }
        break;
        case "1C": {
            datas.add(new Data(type, protocolData.readStr(14)));
        }
        break;
        default:
            throw new RuntimeException("unsupported");
        }

        return datas;
    }

    private static List<Map<String, OIVals>> readRecordRows(ProtocolData protocolData, List<CSD> header) {
        String resultRecordType = protocolData.readStr(2);
        int    len              = protocolData.readInt(2);
        if (!resultRecordType.equalsIgnoreCase("01")) {
            throw new RuntimeException("unsupported");
        }

        List<Map<String, OIVals>> aa = new ArrayList<>();
        for (int i = 0; i < len; i++) {
            Map<String, OIVals> oiValues = new HashMap<>();
            for (CSD csd : header) {
                String oi = csd.getOad().getOi();

                if (csd.getType() == 0) {
                    oiValues.put(oi, new OIVals(oi, readData(protocolData)));
                } else {
                    String type = protocolData.readStr(2);
                    if (!type.equalsIgnoreCase("01")) {
                        throw new RuntimeException("unsupported");
                    }

                    ROAD road = (ROAD) csd.getOad();
                    if (protocolData.readInt(2) != road.getOads().size()) {
                        throw new RuntimeException("unsupported");
                    }
                    for (OAD oad_ : road.getOads()) {
                        oiValues.put(oad_.getOi(), new OIVals(oad_.getOi(), readData(protocolData)));
                    }
                }
            }

            aa.add(oiValues);
        }

        return aa;
    }

    public static List<Map<String, OIVals>> parse(String frame698Str) {
        frame698Str = frame698Str.toUpperCase();

        checkFrame(frame698Str);

        var v = getlinkData(frame698Str);

        ProtocolData protocolData = new ProtocolData(v.getRight());

        String appID   = protocolData.readStr(2);
        String appType = protocolData.readStr(2);

        //System.out.println(linkData);
        if (appID.equalsIgnoreCase("85") && appType.equalsIgnoreCase("03")) {
            String piid = protocolData.readStr(2);
            String oad  = protocolData.readStr(8);

            var header = readRCSD(protocolData);

            return readRecordRows(protocolData, header);
        }
        else {
            //throw new RuntimeException("unsupported");
            return null;
        }
    }
}
